package com.nc.mem.serial.graph.common;

import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;

import com.nc.mem.serial.Serializer;
import com.nc.mem.serial.graph.GraphSerializer;
import com.nc.mem.serial.graph.asm.GraphSerializerFactory;
import com.nc.mem.serial.graph.container.SerializerRegistry;

public final class CompatibleSerializer extends GraphSerializer {

	public static CompatibleSerializer INSTANCE = new CompatibleSerializer();

	final ConcurrentHashMap<Class<?>, GraphSerializer> serializers = new ConcurrentHashMap<Class<?>, GraphSerializer>();

	private CompatibleSerializer() {
	}

	@Override
	public void doRead(final ByteBuffer src, final Object obj) {
		resolve(src).doRead(src, obj);
	}

	@Override
	public void doWrite(final ByteBuffer dest, final Object obj) {
		final GraphSerializer ser = resolve(obj.getClass());
		final String name = obj.getClass().getName();
		// Must write twice, once to be consumed by instantiate and other by doRead, which must
		// re-resolve the serializer before reading the payload.
		StringSerializer.INSTANCE.writeNonRoot(dest, name);
		StringSerializer.INSTANCE.writeNonRoot(dest, obj.getClass().getName());
		ser.doWrite(dest, obj);
	}

	@Override
	public Object instantiate(final ByteBuffer src) {
		final GraphSerializer ser = resolve(src);

		final Object ret = ser.instantiate(src);

		// src.position(p);

		return ret;
	}

	public void onSerializerRegistered(final Class<?> clazz, final Serializer ser) {
		if (ser instanceof GraphSerializer) {
			GraphSerializer s = (GraphSerializer) ser;
			if (s == this) {
				s = (GraphSerializer) GraphSerializerFactory.createByIntrospection(clazz, true, false);
			}
			serializers.put(clazz, s);
		}
	}

	private GraphSerializer resolve(final ByteBuffer src) {
		final String cn = StringSerializer.INSTANCE.readNonRootString(src);
		try {
			final Class<?> clazz = Class.forName(cn);

			return resolve(clazz);
		} catch (final Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	private GraphSerializer resolve(final Class<?> clazz) {
		GraphSerializer ser = serializers.get(clazz);

		if (ser == null) {
			ser = (GraphSerializer) SerializerRegistry.getInstance().getForCompatiblity(clazz);
			if (ser == null || ser == this) {
				ser = (GraphSerializer) GraphSerializerFactory.createByIntrospection(clazz, true, false);
			} else {
				SerializerRegistry.getInstance().removeCompatible(clazz);
			}
			serializers.putIfAbsent(clazz, ser);
		}

		return ser;
	}

}
